#include "common/common.h"
#include "string/stringoutput.h"
#include "Core/CAbstractThread.h"
#include "Core/CAtomicInteger32.h"
#include "Core/CAtomicUnsignedInteger32.h"
#include "testlibpilo.h"

#pragma warning(disable:4505)

using namespace PILO::Core::Thread;

static INT32 g_id = 0;

CAtomicInteger32 g_testint32;
CAtomicUnsignedInteger32 g_testuint32;
CAtomicInteger64 g_testint64;
CAtomicUnsignedInteger64 g_testuint64;


static INT32 case_CAtomicInteger32_add(void* param);
static INT32 case_CAtomicInteger32_subtract(void* param);
static INT32 case_CAtomicUInteger32_add(void* param);
static INT32 case_CAtomicUInteger32_subtract(void* param);
static INT32 case_CAtomicInteger32_other(void* param);
static INT32 case_CAtomicUInteger32_other(void* param);
static INT32 case_CAtomicInteger64_add(void* param);
static INT32 case_CAtomicInteger64_subtract(void* param);
static INT32 case_CAtomicUInteger64_add(void* param);
static INT32 case_CAtomicUInteger64_subtract(void* param);
static INT32 case_CAtomicInteger64_other(void* param);
static INT32 case_CAtomicUInteger64_other(void* param);

static case_testing_t g_cases[] = {
      {1, "add for int32                                     ",               0,case_CAtomicInteger32_add,         -1},
      {2, "subtract for int32                                ",          0,case_CAtomicInteger32_subtract,    -1},
      {3, "add for uint32                                    ",              0,case_CAtomicUInteger32_add,        -1},
      {4, "subtract for uint32                               ",         0, case_CAtomicUInteger32_subtract,  -1},
      {5, "other operation for int32                         ",   0, case_CAtomicInteger32_other,      -1},
      {6, "other operation for uint32                        ",  0, case_CAtomicUInteger32_other,     -1},
      {7, "add for int64                                     ",               0, case_CAtomicInteger64_add,        -1},
      {8, "subtract for int64                                ",          0, case_CAtomicInteger64_subtract,   -1},
      {9, "add for uint64                                    ",              0, case_CAtomicUInteger64_add,       -1},
      {10,"subtract for uint64                               ",         0, case_CAtomicUInteger64_subtract,  -1},
      {11,"other operation for uint32                        ",  0, case_CAtomicInteger64_other,      -1},
      {12,"other operation for uint64                        ",  0, case_CAtomicUInteger64_other,     -1},
};

static INT32 case_CAtomicInteger64_other(void* )
{
    CAtomicInteger64 cl1;

    cl1 = 3000000000000000000LL;
    if (cl1 != 3000000000000000000LL)
    {
        return -1;
    }

    if (cl1 == 3000000000000000001LL)
    {
        return -2;
    }

    if (! cl1)
    {
        return -3;
    }
 
    cl1.assign(0LL);
    if (cl1)
    {
        return -4;
    }
 
    cl1 = 2000000000000000001LL;
    cl1.and(12346456877877LL);
    if (cl1 != (2000000000000000001LL & 12346456877877LL))
    {
         return -5;
    }

    INT64 l1 = cl1;
    CAtomicInteger64 cl2 = -1342343904521LL;

    cl1.or(cl2);
    if (cl1 != (l1 | -1342343904521LL))
    {
        return -6;
    }
 
    cl1 = 4543535205653LL;
    cl1.xor(-10003020LL);
    if (cl1 != (4543535205653LL ^ -10003020LL))
    {
        return -7;
    }

    cl1 = 2000000000000000001LL;
    cl1.compareAndSet(2000000000000000001LL, -600000LL);
    if (cl1 != -600000LL)
    {
        return -8;
    }
    cl1.compareAndSet(-600001LL, -6000LL);
    if (cl1 == -6000LL)
    {
        return -9;
    }

    return 0;
}
static INT32 case_CAtomicUInteger64_other(void* )
{
    CAtomicUnsignedInteger64 cu1;

    cu1 = 8000000000000000000ULL;
    cu1 = 8000000000000000000ULL + 1;
    if (cu1 != 8000000000000000001ULL)
    {
        return -1;
    }

    if (cu1 == 8000000000000000000ULL)
    {
        return -2;
    }

    if (cu1 == (UINT64)1234)
    {
        return -3;
    }
 
    if (! cu1)
    {
        return -4;
    }
 
    cu1.assign(0);
    if (cu1)
    {
        return -5;
    }
 
    cu1 = 8000000000000000000ULL;
    CAtomicUnsignedInteger64 cu2 = 6334000000000000ULL;

    cu1.and(cu2);
    if (cu1 != (8000000000000000000ULL & 6334000000000000ULL))
    {
        return -6;
    }

    cu1 = 8000000000000000000ULL;
    cu2 = 6334000000000000ULL;
    cu1.or(cu2);
    if (cu1 != (8000000000000000000ULL | 6334000000000000ULL))
    {
        return -7;
    }
 
    cu1 = 8000000000000000000ULL;
    cu2 = 6334000000000000ULL;
    cu1.xor(cu2);
    if (cu1 != (8000000000000000000ULL ^ 6334000000000000ULL))
    {
        return -8;
    }
 
    cu1 = 6334000000000000ULL;;
    cu1.compareAndSet(cu2, 6334000000000000ULL);
    if (cu1 != 6334000000000000ULL)
    {
        return -9;
    }
 
    return 0;
}


static INT32 case_CAtomicUInteger32_other(void* )
{
    CAtomicUnsignedInteger32 cu1;

    cu1 = 3000000000;
    cu1 = cu1 + 1;
    if (cu1 != 3000000001U)
    {
        return -1;
    }

    if (cu1 == 767512340U)
    {
        return -2;
    }

    if (cu1 == (UINT32)1234)
    {
        return -3;
    }

    if (! cu1)
    {
        return -4;
    }

    cu1.assign(0);
    if (cu1)
    {
        return -5;
    }

    cu1 = 856;
    cu1.and(123);
    if (cu1 != 88U)
    {
        return -6;
    }

    cu1 = 13423421;
    cu1.or((UINT32)-32423423);
    if (cu1 != (UINT32)(13423421 | -32423423))
    {
        return -7;
    }

    cu1 = 454353523;
    cu1.xor((UINT32)-10003020);
    if (cu1 != (UINT32)(454353523 ^ -10003020))
    {
        return -8;
    }

    cu1 = 10000;
    cu1.compareAndSet(10000U, 600000U);
    if (cu1 != 600000U)
    {
        return -9;
    }

    return 0;
}

static INT32 case_CAtomicInteger32_other(void*)
{
    CAtomicInteger32 ci1;

    ci1 = 76751234;
    if (ci1 != 76751234)
    {
        return -1;
    }

    if (ci1 == 767512340)
    {
        return -2;
    }

    if (! ci1)
    {
        return -3;
    }

    ci1.assign(0);
    if (ci1)
    {
        return -4;
    }

    ci1 = 856;
    ci1.and(123);
    if (ci1 != 88)
    {
        return -5;
    }

    ci1 = 13423421;
    ci1.or(-32423423);
    if (ci1 != (13423421 | -32423423))
    {
        return -6;
    }

    ci1 = 454353523;
    ci1.xor(-10003020);
    if (ci1 != (454353523 ^ -10003020))
    {
        return -7;
    }
    ci1 = 10000;
    ci1.compareAndSet(10000, -600000);
    if (ci1 != -600000)
    {
        return -8;
    }

    return 0;
}

#ifdef  WINDOWS



//64bit int test

static THREAD_ROUTINE_RETURN_TYPE THREAD_ROUTINE_CALL atomicSubUInt64(void *)
{
    int i=0;
    for(i=0;i<  100000000;++i){
        g_testuint64.subtract(4000000000);
    }
    _endthreadex( 0 );
    return 0;
}

static INT32 case_CAtomicUInteger64_subtract(void*)
{
    HANDLE  hdl[20];
    int i;

    g_testuint64 = 8000000000000000000ULL;

    for( i=0;i<20;++i){
        hdl[i] = (HANDLE) _beginthreadex(0,0, atomicSubUInt64,0,0,0);
        if  (hdl[i] == (HANDLE) -1) {
            return -1;
        }
    }

    int rc = WaitForMultipleObjects(20, hdl, TRUE, INFINITE);
    if (rc != WAIT_OBJECT_0)
    {
        return -2;
    }

    if (g_testuint64 != 0ULL) {
        return -3;
    }

    return 0;
}

static THREAD_ROUTINE_RETURN_TYPE THREAD_ROUTINE_CALL atomicAddUInt64(void *)
{
    int i=0;
    for(i=0;i<  100000000;++i){
        g_testuint64.add(4000000000);
    }
    _endthreadex( 0 );
    return 0;
}

static INT32 case_CAtomicUInteger64_add(void* )
{
    HANDLE  hdl[20];
    int i;
    for( i=0;i<20;++i){
        hdl[i] = (HANDLE) _beginthreadex(0,0, atomicAddUInt64,0,0,0);
        if  (hdl[i] == (HANDLE) -1) {
            return -1;
        }
    }

    int rc = WaitForMultipleObjects(20, hdl, TRUE, INFINITE);
    if (rc != WAIT_OBJECT_0)
    {
        return -2;
    }

    if (g_testuint64 != 8000000000000000000ULL) {
                        
        return -3;
    }

    return 0;
}

static THREAD_ROUTINE_RETURN_TYPE THREAD_ROUTINE_CALL atomicSubInt64(void *)
{
    int i=0;
    for(i=0;i<  100000000;++i){
        g_testint64.subtract(100);
    }
    _endthreadex( 0 );
    return 0;
}

static INT32 case_CAtomicInteger64_subtract(void* )
{
    HANDLE  hdl[20];
    int i;

    g_testint64 = 200000000000LL;

    for( i=0;i<20;++i){
        hdl[i] = (HANDLE) _beginthreadex(0,0, atomicSubInt64,0,0,0);
        if  (hdl[i] == (HANDLE) -1) {
            return -1;
        }
    }

    int rc = WaitForMultipleObjects(20, hdl, TRUE, INFINITE);
    if (rc != WAIT_OBJECT_0)
    {
        return -2;
    }

    if (g_testint64 != 0LL) {
        return -3;
    }

    return 0;
}

static THREAD_ROUTINE_RETURN_TYPE THREAD_ROUTINE_CALL atomicAddInt64(void *)
{
    int i=0;
    for(i=0;i<  10000000;++i){
        g_testint64.add(1000);
    }
    _endthreadex( 0 );
    return 0;
}

static INT32 case_CAtomicInteger64_add(void* )
{
    HANDLE  hdl[20];
    int i;
    for( i=0;i<20;++i){
        hdl[i] = (HANDLE) _beginthreadex(0,0, atomicAddInt64,0,0,0);
        if  (hdl[i] == (HANDLE) -1) {
            return -1;
        }
    }

    int rc = WaitForMultipleObjects(20, hdl, TRUE, INFINITE);
    if (rc != WAIT_OBJECT_0)
    {
        return -2;
    }

    if (g_testint64 != 200000000000LL) {
        return -3;
    }

    return 0;
}

//32bit int test
 static THREAD_ROUTINE_RETURN_TYPE THREAD_ROUTINE_CALL atomicSubUInt32(void *)
 {
     int i=0;
     for(i=0;i<  10000000;++i){
         g_testint32.subtract(20);
     }
     _endthreadex( 0 );
     return 0;
 }

static INT32 case_CAtomicUInteger32_subtract(void* )
{

         g_testint32.assign(4000000000);
     
         HANDLE  hdl[20];
         int i;
         for( i=0;i<20;++i){
             hdl[i] = (HANDLE) _beginthreadex(0,0, atomicSubUInt32,0,0,0);
             if  (hdl[i] == (HANDLE) -1) {
                 return -1;
             }
         }
     
         int rc = WaitForMultipleObjects(20, hdl, TRUE, INFINITE);
         if (rc != WAIT_OBJECT_0)
         {
             return -2;
         }
     
         if (g_testint32 != 0) {
             return -3;
         }

    return 0;
}



static THREAD_ROUTINE_RETURN_TYPE THREAD_ROUTINE_CALL atomicAddUInt32(void *)
{
    int i=0;
    for(i=0;i<  10000000;++i){
        g_testuint32.add(20);
    }
    _endthreadex( 0 );
    return 0;
}

static INT32 case_CAtomicUInteger32_add(void* )
{
    HANDLE  hdl[20];
    int i;
    for( i=0;i<20;++i){
        hdl[i] = (HANDLE) _beginthreadex(0,0, atomicAddUInt32,0,0,0);
        if  (hdl[i] == (HANDLE) -1) {
            return -1;
        }
    }

    int rc = WaitForMultipleObjects(20, hdl, TRUE, INFINITE);
    if (rc != WAIT_OBJECT_0)
    {
        return -2;
    }

    if (g_testuint32 != 4000000000U) {
        return -3;
    }

    return 0;
}



//32bit int test
 static THREAD_ROUTINE_RETURN_TYPE THREAD_ROUTINE_CALL atomicAddInt32(void *)
 {
     int i=0;
     for(i=0;i<  10000000;++i){
         g_testint32.add(10);
     }
     _endthreadex( 0 );
     return 0;
 }



static INT32 case_CAtomicInteger32_add(void* )
{
     HANDLE  hdl[20];
     int i;
     for( i=0;i<20;++i){
         hdl[i] = (HANDLE) _beginthreadex(0,0, atomicAddInt32,0,0,0);
         if  (hdl[i] == (HANDLE) -1) {
             return -1;
         }
     }
 
     int rc = WaitForMultipleObjects(20, hdl, TRUE, INFINITE);
     if (rc != WAIT_OBJECT_0)
     {
         return -2;
     }
 
     if (g_testint32 != 2000000000) {
         return -3;
     }

    return 0;
}

 static THREAD_ROUTINE_RETURN_TYPE THREAD_ROUTINE_CALL atomicSubInt32(void *)
 {
     int i=0;
     for(i=0;i<  10000000;++i){
         g_testint32.subtract(10);
     }
     _endthreadex( 0 );
     return 0;
 }

static INT32 case_CAtomicInteger32_subtract(void* )
{

     g_testint32.assign(0);
 
     HANDLE  hdl[20];
     int i;
     for( i=0;i<20;++i){
         hdl[i] = (HANDLE) _beginthreadex(0,0, atomicSubInt32,0,0,0);
         if  (hdl[i] == (HANDLE) -1) {
             return -1;
         }
     }
 
     int rc = WaitForMultipleObjects(20, hdl, TRUE, INFINITE);
     if (rc != WAIT_OBJECT_0)
     {
         return -2;
     }
 
     if (g_testint32 != -2000000000) {
         return -3;
     }

    return 0;
}


#else
//64bit int test
static static void* atomicSubUInt64(void *)
{
    int i=0;
    for(i=0;i<  100000000;++i){
        g_testuint64.subtract(4000000000);
    }
    return 0;
}

static INT32 case_CAtomicUInteger64_subtract(void*)
{
    pthread_t  tid[20];

    g_testuint64 = 8000000000000000000ULL;

    int i;
    for( i=0;i<20;++i){
        if (0 != pthread_create(&tid[i], 0, atomicSubUInt64,0)) {
            return -1;
        }
    }

    for (i=0; i<20; ++i) {
        pthread_join(tid[i], 0);
    }

    if (g_testuint64 != 0ULL) {
        return -3;
    }

    return 0;
}



static THREAD_ROUTINE_RETURN_TYPE THREAD_ROUTINE_CALL atomicAddUInt64(void *)
{
    int i;
    for(i=0;i<  100000000;++i){
        g_testuint64.add(4000000000);
    }
    return 0;
}
static INT32 case_CAtomicInteger64_subtract(void* )
{
    pthread_t  tid[20];

    g_testint64 = 0ULL;

    int i;
    for( i=0;i<20;++i){
        if (0 != pthread_create(&tid[i], 0, atomicAddUInt64,0)) {
            return -1;
        }
    }

    for (i=0; i<20; ++i) {
        pthread_join(tid[i], 0);
    }


    if (g_testuint64 != 8000000000000000000ULL) {
        return -3;
    }

    return 0;
}




static static void* atomicSubInt64(void *)
{
    int i=0;
    for(i=0;i<  100000000;++i){
        g_testint64.subtract(100);
    }
    return 0;
}

static INT32 case_CAtomicInteger64_subtract(void* )
{
    pthread_t  tid[20];

    g_testint64 = 200000000000LL;

    int i;
    for( i=0;i<20;++i){
        if (0 != pthread_create(&tid[i], 0, atomicSubInt64,0)) {
            return -1;
        }
    }

    for (i=0; i<20; ++i) {
        pthread_join(tid[i], 0);
    }


    if (g_testint64 != 0LL) {
        return -3;
    }

    return 0;
}


static static void* atomicAddInt64(void *)
{
    int i;
    for(i=0;i<  10000000;++i){
        g_testint64.add(1000);
    }
    return 0;
}

static INT32 case_CAtomicInteger64_add(void* )
{

    pthread_t  tid[20];

    int i;
    for( i=0;i<20;++i){
        if (0 != pthread_create(&tid[i], 0, atomicAddInt64,0)) {
            return -1;
        }
    }

    for (i=0; i<20; ++i) {
        pthread_join(tid[i], 0);
    }


    if (g_testint64 != 200000000000LL) {
        return -3;
    }

    return 0;
}


//32bit int test
static void* atomicAddUInt32(void *)
{
    int i;
    for(i=0;i<  10000000;++i){
        g_testuint32.add(20);
    }
    return 0;
}

static INT32 case_CAtomicUInteger32_add(void* )
{

    pthread_t  tid[20];

    int i;
    for( i=0;i<20;++i){
        if (0 != pthread_create(&tid[i], 0, atomicAddInt32,0)) {
            return -1;
        }
    }

    for (i=0; i<20; ++i) {
        pthread_join(tid[i], 0);
    }


    if (4000000000 != g_testint32) {
        return -2;
    }

    return 0;
}

static void* atomicSubUInt32(void *)
{

    int i=0;
    for(i=0;i<  10000000;++i){
        g_testint32.subtract(20);
    }
    return 0;
}

static INT32 case_CAtomicUInteger32_subtract(void* )
{

    pthread_t  tid[20];

    g_testuint32 = 4000000000;

    int i;
    for( i=0;i<20;++i){
        if (0 != pthread_create(&tid[i], 0, atomicAddInt32,0)) {
            return -1;
        }
    }

    for (i=0; i<20; ++i) {
        pthread_join(tid[i], 0);
    }


    if (0 != g_testint32) {
        return -2;
    }

    return 0;
}



//32bit int test
static void* atomicAddInt32(void *)
{

    int i=0;
    for(i=0;i<  10000000;++i){
        g_testint32.add(10);
    }
    return 0;
}
static INT32 case_CAtomicInteger32_add(void* )
{

    pthread_t  tid[20];

    int i;
    for( i=0;i<20;++i){
        if (0 != pthread_create(&tid[i], 0, atomicAddInt32,0)) {
            return -1;
        }
    }

    for (i=0; i<20; ++i) {
        pthread_join(tid[i], 0);
    }


    if (2000000000 != g_testint32) {
        return -2;
    }

    return 0;
}

static void* atomicSubInt32(void *)
{

    int i=0;
    for(i=0;i<  10000000;++i){
        g_testint32.subtract(10);
    }
    return 0;
}
static INT32 case_CAtomicInteger32_subtract(void* )
{

    pthread_t  tid[20];

    g_testint32 = 0;

    int i;
    for( i=0;i<20;++i){
        if (0 != pthread_create(&tid[i], 0, atomicAddInt32,0)) {
            return -1;
        }
    }

    for (i=0; i<20; ++i) {
        pthread_join(tid[i], 0);
    }


    if (-2000000000 != g_testint32) {
        return -2;
    }

    return 0;
}

#endif




INT32 test_atomictypes_Functional(INT32 id, int breakOnErrorOccured)
{
    g_id = id;

    FormatOutputToConsole("Testing Part_%d <atomictypes.h>\n", id);

    int i=0;
    for (i=0; i<MCF_COUNT_OF(g_cases); i++) {
        case_testing_t * caseParam = &g_cases[i];
        ASSERT(caseParam != NULL);
        FormatOutputToConsole(" #[%03d.%03d]: Testing %s",g_id, caseParam->ct_id, caseParam->ct_decription);

        caseParam->ct_result = caseParam->ct_function(static_cast<case_testing_t *>(caseParam));
        if (caseParam->ct_result == 0) { //ok 
            FormatOutputToConsole("[PASSED]\n");
        } else {
            FormatOutputToConsole("[FAILED:%d]\n",caseParam->ct_result);
            if (breakOnErrorOccured) {
                ASSERT(false);
            }
        }
    }


    FormatOutputToConsole("Result of Part_%d <atomictypes.h> total %d,   failed %d\n\n",id, MCF_COUNT_OF(g_cases), failedOFCases(g_cases,MCF_COUNT_OF(g_cases) ));
    return 0;
}

